import { CanActivate, ExecutionContext, ForbiddenException, Injectable, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLE_KEY as ROLE_KEYS, ROLE_MENU } from '@/common/config/role.menu';
import { UserService } from '@/modules/user/user.service';
import { intersection } from '@/utils/intersection';
import { ROLE_KEY, RoleOptions } from '@/utils/metadata/role';
import { ValidatePublic } from './validatePublic';

@Injectable()
export class RoleGuard implements CanActivate {
    constructor(
        private reflector: Reflector,
        private readonly validatePublic: ValidatePublic,
        private readonly userService: UserService
    ) {}

    async canActivate(context: ExecutionContext): Promise<boolean> {
        const isPublic = this.validatePublic.isPublic(context);
        if (isPublic) {
            return true;
        }

        const roleOptions = this.reflector.getAllAndOverride<RoleOptions>(ROLE_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);
        if (!roleOptions) {
            return true;
        }

        const { userId } = context.switchToHttp().getRequest().body || {};
        const roles = await this.userService.getUserRoleById(userId);
        if (roles.length === 0) throw new UnauthorizedException();

        const hasRole = this.hasRole(roleOptions, roles);
        if (!hasRole) {
            throw new ForbiddenException('insufficient authority');
        }

        return true;
    }

    private hasRole(roleOptions: RoleOptions, userRole: ROLE_KEYS[]): boolean {
        if (roleOptions instanceof Array) {
            return intersection(roleOptions, userRole).length > 0;
        } else if (typeof roleOptions === 'object' && roleOptions !== null) {
            const { include = Object.values(ROLE_MENU), exclude = [] } = roleOptions;
            const remainRoles = Object.values(ROLE_MENU)
                .filter((item) => !exclude.includes(item))
                .filter((item) => include.includes(item));
            return intersection(remainRoles, userRole).length > 0;
        }
        return false;
    }
}
